Skip to content

CORS: reject '*' with credentials, add Vary: Origin and an origin matcher - #1614

Merged
thboileau-git merged 2 commits into
3.0from
feature/cors-3.0
Oct 31, 2026
Merged

thboileau-git merged 2 commits into
3.0from
feature/cors-3.0

Conversation

@thboileau-git

Copy link
Copy Markdown
Collaborator

The aim

Refs #1611

Make CORS safe behind shared caches and with credentials, following the Fetch standard (CORS protocol, "CORS protocol and HTTP caches") and RFC 9110 section 12.5.5.

  • Vary: Origin was never sent (shared caches could mix origins).
  • * allowed together with credentials makes the filter echo any request origin with Access-Control-Allow-Credentials: true.
  • Changing the configuration of a CorsFilter after its first request had no effect.

The solution

Breaking:

  • Allowing credentials with the * origin is rejected with an IllegalStateException, when CorsService creates its filter or when CorsFilter starts (so the order of the setters doesn't matter). If the filter could not be started, afterHandle adds no CORS headers (fails closed).
  • CorsFilter setters rebuild the internal helper, so changes after the first request are applied.

Non breaking:

  • Vary: Origin is added when the answer depends on the origin (credentials, or allowed origins without *), including requests without Origin; none for a static *.
  • New setAllowedOriginMatcher(Predicate<String>) on CorsService, CorsFilter, CorsResponseHelper; the response carries the exact request origin.
  • Javadoc references the Fetch standard.
  • Restlet.handle(): no more NullPointerException when a restlet without context cannot be started (found while testing the fail-closed path).

Not done: Private Network Access; checks of * in Allow-Headers/Allow-Methods (Restlet never writes a literal * there).

Reproduction

new CorsService() with setAllowedCredentials(true) and the default origins now fails at createInboundFilter with a message asking to list the origins.

Check-list

  • PR size
    • Under 300 lines ✅
    • Can't be split without complicating the process even more
  • Tests
    • Added
  • Doc
    • Added (changes.md, Javadoc)
  • Reviewer
    • Asked for a review

Companions for 2.7 and 2.6 (non breaking part only).

🤖 Generated with Claude Code

thboileau and others added 2 commits October 31, 2026 18:09
…cher

- Allowing credentials together with the '*' origin is rejected with an
  IllegalStateException (CorsService.createInboundFilter, CorsFilter.start);
  CorsFilter adds no CORS headers with such a configuration.
- Vary: Origin is added when the CORS answer depends on the request origin
  (origin allow-list or credentials), including for requests without Origin;
  a static '*' without credentials sends no Vary (Fetch standard).
- CorsFilter setters now apply after the first request (the helper is rebuilt).
- Optional origin matcher (setAllowedOriginMatcher).
- Restlet.handle() no longer throws a NullPointerException when a restlet without
  context cannot be started.
- Javadoc references the Fetch standard instead of the W3C CORS draft.

Refs #1611

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
@sonarqubecloud

Copy link
Copy Markdown

@thboileau-git
thboileau-git merged commit d1dba11 into 3.0 Oct 31, 2026
3 of 5 checks passed
@thboileau-git
thboileau-git deleted the feature/cors-3.0 branch October 31, 2026 17:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants